iT邦幫忙

2022 iThome 鐵人賽

DAY 9
0
Mobile Development

【Kotlin Notes And JetPack】Build an App系列 第 9

Day 9.【Classes and Objects】Delegate Properties

  • 分享至 

  • xImage
  •  

今天的筆記主要是以 kotlin 會透過什麼方式來實現委託,以下如有解釋不清或是描述錯誤的地方還請大家多多指教:

什麼?

先來淺談什麼是 delegate,Delegate Pattern 是設計模式中的其中一項,是接受請求的對象將請求的事情委託給另一個對象來處理,是什麼意思呢?直接來看看範例會更清楚:

interface Base {
    fun count()
}

class Counter(val money: Int) : Base {
    override fun count() { print(money*5) }
}

class Bank(val counter: Base) : Base by counter

fun main() {
    val b = Counter(20)
    Bank(b).count()
	// get 100
}

class Bank implement Base 這個 interface,但實作的部分交由 Counter 來處理,Kotlin 是使用 by 來執行委託:

| Overriding member

當請求的對象也實作方法時,實作的內容會覆蓋掉被委託者做的事情:

interface Base {
    fun count()
    fun countAgain()
}

class Counter(val money: Int) : Base {
    override fun count() { print(money*5) }
    override fun countAgain() { print(money*10) }
}

class Bank(val counter: Base) : Base by counter {
    override fun count() { print("abc") }
}

fun main() {
    val b = Counter(20)
    Bank(b).count()
    Bank(b).countAgain()
	// origin: 100200
	// override: abc100
}
interface Base {
    val message: String
    fun count()
}

class Counter(val money: Int) : Base {
    override val message = "Counter count money : $money"
    override fun count() { println(message) }
}

class Bank(val counter: Base) : Base by counter {
    // This property is not accessed from counter's implementation of `count`
    override val message = "Message of Bank"
}

fun main() {
    val b = Counter(20)
    val bank = Bank(b)
    bank.count()
    println(bank.message)
	// Counter count money : 20
	// Message of Bank
}

Bank 覆寫了 message 的值,並不是覆寫方法,所以執行 count() 的時候還是以 Counter 覆寫的值為主。

| Delegate Properties

有些屬性雖然也是可以在需要用到它的時候再建立出來,但也可以透過委託的方式先進行實作,等到需要用的時候再使用它,以下是語法使用方式:

val/var <property name>: <Type> by <expression>

屬性委託不需要實作任何 interface,但委託的的對象需要提供 getValue() ,如果是 var 還是需要提供 setValue() :

class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name}' in $thisRef.")
    }
}

class Example {
	// e.g.
	var name: String by Delegate()
	print(name) // get 'Example, thank you for delegating name to me!'
	
	name = "my name"
	print(name) // get 'my name has been assigned to name in Example'
}
  • thisRef 為進行委託的對象,他的 type 需要跟 property owner 依樣或是 supertype
  • property 的 type u 一定要是 KProperty<*> 或是 supertype

| Standard Delegates

Kotlin 本身內置一些透過工廠方法實現屬性委託的方法:

  • Lazy properties:只會在初始調用的時候執行一次,之後調用都是 get 已執行完的結果
val lazyValue: String by lazy {
    println("computed!") // 此動作只會在第一次的時候執行
    "Hello"
}

fun main() {
    println(lazyValue)
    println(lazyValue)
	// computed! 
	// Hello
	// Hello
}
  • Observable properties:監聽當個屬性的變化,屬性變化時執行預設的動作
class User {
    var name: String by Delegates.observable("default") {
        prop, old, new ->
        println("$old -> $new")
    }
}

fun main() {
    val user = User()
    user.name = "Nami"
    user.name = "Luffy"
	// first line print: default -> Nami
	// second line print: Nami -> Luffy
}
  • Storing properties in a map:使用映射的方式設定屬性的值,取代個別帶入參數
class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int     by map
}

fun main(args: Array<String>) {
   val user = User(mapOf(
	    "name" to "Luffy",
	    "age"  to 25
	 ))

    println(user.name) // get "Luffy"
	println(user.age)  // get 25
}

| Delegating to another property

自 Kotlin 1.4 後,屬性委託可以委託給另一個屬性,上面看到的範例都是委託給另一個 class 或是 function,可使用的情境有:

  • a top-level properties
  • 同個 class 的 member 或 extension properties
  • 不同 class 的 member 或 extension properties

在要委託的屬性前加上 ::,根據以上三個使用情境,:: 前綴加的名稱會有所不同:

var top: Int = 0
class ClassWithDelegate(val anotherClassInt: Int)

class User(var member: Int, val another: ClassWithDelegate) {
    var delegatedToMember: Int by this::member
    var delegatedToTopLevel: Int by ::top

    val delegatedToAnotherClass: Int by another::anotherClassInt
}
var User.extDelegated: Int by ::top
  • top-level:::top
  • 同個 class:this::member
  • 不同 class:another::anotherClassInt

| Translation rules for delegated properties

我們來看看底層的解析規則吧!compiler 會將每個委託屬性生成一個輔助屬性並將其委託給這個輔助屬性,以下面的範例來看,屬性 name 在 compile 生成了隱藏的屬性 name$delegate,而訪問器的代碼簡單的委託給這個附加的屬性。

class User {
    var name: Type by CustomDelegate()
}

// this code is generated by the compiler instead:
class User {
    private val name$delegate = CustomDelegate()
    var name: Type
    get() = name$delegate.getValue(this, this::name)
    set(value: Type) = name$delegate.setValue(this, this::name, value)
}

compiler 生成了所有有關 name 的資訊:第一個 argument 的 this 是 class c 的 instance,this::nameKProperty 的反射對象,描述 name 本身。

那屬性委託給另一個屬性 compile 後會變怎麼樣呢?

class User<Type> {
    private var impl: Type = ...
    var prop: Type by ::impl
}

// this code is generated by the compiler instead:
class User<Type> {
    private var impl: Type = ...

    var prop: Type
        get() = impl
        set(value) {
            impl = value
        }

	// This method is needed only for reflection
    fun getProp$delegate(): Type = impl 
}

pro 直接調用 impl 的值,跳過 getValuesetValue ,因此不需要 KProperty

如何?

那專案會有那些地方會使用到 delegate 呢?
我會透過 by lazy 來初始化已實作好的 Adapter 以及 by viewModel 來產出 ViewModel:

class HomeFragment : Fragment() {
	private val viewModel by viewModels<MyViewModelName>()
	private val listAdapter by lazy { MyAdapterName() }
	...
}

Reference

Official Kotlin
簡單教程


上一篇
Day 8.【Classes and Objects】Extensions
下一篇
Day 10.【Corountines】Coroutines Basic
系列文
【Kotlin Notes And JetPack】Build an App30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言